﻿namespace VirtualTemplates
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Web.Hosting;

    /// <summary>
    /// This virtual directory merges a base directory with an additional directory.
    /// </summary>
    internal class MergedVirtualDirectory : VirtualDirectory
    {
        private readonly VirtualDirectory baseDirectory;

        private readonly VirtualDirectory additionalDirectory;

        /// <summary>
        /// Initializes a new instance of the <see cref="MergedVirtualDirectory"/> class.
        /// </summary>
        /// <param name="baseDirectory">The base directory.</param>
        /// <param name="additionalDirectory">The additional directory.</param>
        public MergedVirtualDirectory(VirtualDirectory baseDirectory, VirtualDirectory additionalDirectory) : base(baseDirectory.VirtualPath)
        {
            Contract.Requires(baseDirectory != null);
            Contract.Requires(additionalDirectory != null);

            this.baseDirectory = baseDirectory;
            this.additionalDirectory = additionalDirectory;
        }

        /// <summary>
        /// Gets a list of the files and subdirectories contained in this virtual directory.
        /// </summary>
        /// <returns>An object implementing the <see cref="T:System.Collections.IEnumerable"/> interface containing <see cref="T:System.Web.Hosting.VirtualFile"/> and <see cref="T:System.Web.Hosting.VirtualDirectory"/> objects.</returns>
        public override IEnumerable Children
        {
            get { return this.Directories.Cast<object>().Concat(this.Files.Cast<object>()); }
        }

        /// <summary>
        /// Gets a list of all the subdirectories contained in this directory.
        /// </summary>
        /// <returns>An object implementing the <see cref="T:System.Collections.IEnumerable"/> interface containing <see cref="T:System.Web.Hosting.VirtualDirectory"/> objects.</returns>
        public override IEnumerable Directories
        {
            get { return MergedDirectories(this.baseDirectory.Directories.Cast<VirtualDirectory>(), this.additionalDirectory.Directories.Cast<VirtualDirectory>()).OrderBy(d => d.Name); }
        }

        /// <summary>
        /// Gets a list of all files contained in this directory.
        /// </summary>
        /// <returns>An object implementing the <see cref="T:System.Collections.IEnumerable"/> interface containing <see cref="T:System.Web.Hosting.VirtualFile"/> objects.</returns>
        public override IEnumerable Files
        {
            get { return MergedFiles(this.baseDirectory.Files.Cast<VirtualFile>(), this.additionalDirectory.Files.Cast<VirtualFile>()).OrderBy(f => f.Name); }
        }

        /// <summary>
        /// Merges base directories with additional dictionaries.
        /// </summary>
        /// <param name="baseDirectories">The base directories.</param>
        /// <param name="additionalDirectories">The additional directories.</param>
        /// <returns>The merged dictionaries</returns>
        private static IEnumerable<VirtualDirectory> MergedDirectories(IEnumerable<VirtualDirectory> baseDirectories, IEnumerable<VirtualDirectory> additionalDirectories)
        {
            var result = new List<VirtualDirectory>();
            var directories = new List<VirtualDirectory>(additionalDirectories);

            foreach (var baseDir in baseDirectories)
            {
                var additionalDir = directories.SingleOrDefault(d => d.Name.Equals(baseDir.Name, StringComparison.InvariantCultureIgnoreCase));

                if (additionalDir == null)
                {
                    result.Add(baseDir);
                }
                else
                {
                    directories.Remove(additionalDir);
                    result.Add(new MergedVirtualDirectory(baseDir, additionalDir));
                }
            }

            result.AddRange(directories);
            return result;
        }

        /// <summary>
        /// Merges base files with additional files.
        /// </summary>
        /// <param name="baseFiles">The base files.</param>
        /// <param name="additionalFiles">The additional files.</param>
        /// <returns>The merged files.</returns>
        private static IEnumerable<VirtualFile> MergedFiles(IEnumerable<VirtualFile> baseFiles, IEnumerable<VirtualFile> additionalFiles)
        {
            foreach (var baseFile in baseFiles)
            {
                yield return baseFile;
            }

            var fileExists = new Func<string, bool>(name => baseFiles.Any(f => f.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)));

            foreach (var additionalFile in from a in additionalFiles where !fileExists(a.Name) select a)
            {
                yield return additionalFile;
            }
        }
    }
}
